package jd.monitor.agent;

import java.io.File;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import jd.monitor.attach.config.BeanConfig;
import jd.monitor.attach.config.BeanContext;
import jd.monitor.attach.config.BeanRoot;
import jd.monitor.attach.config.MethodConfig;
import jd.monitor.attach.util.PathUtil;

/**
 * 
 * @author wangyankai
 *
 */
public class MonitorTransformer implements ClassFileTransformer {

	// private final static Logger logger =
	// LoggerFactory.getLogger(MonitorTransformer.class);
	private ClassLoader classLoader;

	public MonitorTransformer(ClassLoader classLoader1) {
		classLoader = classLoader1;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.
	 * ClassLoader, java.lang.String, java.lang.Class,
	 * java.security.ProtectionDomain, byte[])
	 */
	public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
			ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
		// 将‘/’替换为‘.’m比如monitor/agent/Mytest替换为monitor.agent.Mytest
		className = className.replace("/", ".");
		BeanRoot beanRoot = (BeanRoot) BeanContext.getContext().get(BeanContext.BEANROOT);
		if (beanRoot != null) {
			HashSet<String> beanNames = beanRoot.getBeanNames();
			// 同一个classLoader， 通过instrumentation进来的class路径用‘/’分割
			if (loader.equals(classLoader) && beanNames.contains(className)) {
				try {

					// 直接替换类
					BeanConfig beanConfigR = beanRoot.getBeanByName(className);
					String rFile = beanConfigR.getReplaceFile();
					if (rFile != null) {
						System.out.println("rFile = " + rFile);
						// 编译java源文件
						if (rFile.endsWith(".java")) {
							byte[] classByte = javaCodeToObject(rFile, loader);
							System.out.println("classByte ="+classByte);
							return classByte;
						} else {
							// 读取class文件
							URL furl = loader.getResource(rFile);
							System.out.println("furl = " + furl);
							if (furl != null) {
								byte[] newByte = FileUtils.readFileToByteArray(new File(furl.toURI()));
								System.out.println("newByte = " + newByte);
								return newByte;
							}
						}
					}

					// 替换方法体
					return replaceMethodBody(className, beanRoot);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return classfileBuffer;
	}

	public byte[] javaCodeToObject(String fileName, ClassLoader loader) throws Exception {

		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
		DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector();

		List<String> options = new ArrayList();
		// options.add("-Xlint:all");
		// options.add("-g:lines");
		// options.add("-deprecation");
		options.add("-source");
		options.add("1.6");
		options.add("-target");
		options.add("1.6");
		options.add("-classpath");
		options.add(buildClassPath().toString());

		Iterable<? extends JavaFileObject> compUnits = stdManager
				.getJavaFileObjects(new File(loader.getResource(fileName).toURI()));
		CompilationTask task = compiler.getTask(null, stdManager, diagnostics, options, null, compUnits);
		Boolean callResult = task.call();
		System.out.println(" callResult = "+ callResult);
		if ((!callResult.booleanValue()) || (containsErrors(diagnostics))) {
			for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
				System.out.println(diagnostic.getMessage(null));
			}
			return null;
		}

		try {
			stdManager.flush();
			stdManager.close();
		} catch (IOException exp) {
			exp.printStackTrace();
		}

		String classFile = fileName.substring(0, fileName.lastIndexOf(".")) + ".class";
		return readClassFileToByte(loader, classFile);

	}

	private StringBuffer buildClassPath() {
		StringBuffer classpath = new StringBuffer();
		String pathForJar = PathUtil.getPathForJar();
		// Jar包路径
		String pathJar = pathForJar.substring(0, pathForJar.lastIndexOf("/") + 1);
		File pathJarD = new File(pathJar);
		for (String name : pathJarD.list()) {
			classpath.append(File.pathSeparator).append(pathJar).append(name);
		}
		String classDir;
		// 类路径
		int webIndex = pathForJar.lastIndexOf("WEB-INF");
		if (webIndex > 0) {
			classDir = pathForJar.substring(0, webIndex + 7) + "/classes/";
			File classes = new File(classDir);
			for (File f : classes.listFiles()) {
				File cf = getFile(f);
				classpath.append(File.pathSeparator).append(cf.getAbsolutePath());
			}
			classpath.append(File.pathSeparator).append(System.getProperty("java.class.path"));
		}
		return classpath;
	}

	private byte[] readClassFileToByte(ClassLoader loader, String fileName) throws Exception {
		URL classFileUrl = loader.getResource(fileName);
		System.out.println("classFileUrl = " + classFileUrl);
		if (classFileUrl != null) {
			byte[] newByte = FileUtils.readFileToByteArray(new File(classFileUrl.toURI()));
			return newByte;
		}
		return null;
	}

	private File getFile(File classes) {
		if (classes.isFile())
			return classes;
		for (File f : classes.listFiles()) {
			if (f.isDirectory()) {
				getFile(f);
			} else {
				return f;
			}
		}
		return classes;
	}

	private boolean containsErrors(DiagnosticCollector<?> diagnostics) {
		for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
			if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
				return true;
			}
		}
		return false;
	}

	@Deprecated
	private byte[] replaceMethodBody(String className, BeanRoot beanRoot)
			throws NotFoundException, CannotCompileException, IOException {

		// 用于取得字节码类，必须在当前的classpath中，使用全称 ,这部分是关于javassist的知识
		ClassPool cp = ClassPool.getDefault();
		String pathForJar = PathUtil.getPathForJar();
		// Jar包路径
		String pathJar = pathForJar.substring(0, pathForJar.lastIndexOf("/") + 1) + "*";
		System.out.println(" pathJar = " + pathJar);
		cp.insertClassPath(pathJar);
		String classDir;
		// 类路径
		int webIndex = pathForJar.lastIndexOf("WEB-INF");
		if (webIndex > 0) {
			classDir = pathForJar.substring(0, webIndex + 7) + "/classes/";
			System.out.println(" classDir = " + classDir);
			cp.insertClassPath(classDir);
		}
		CtClass ctclass = cp.get(className);
		if (ctclass.isFrozen())
			ctclass.defrost();

		BeanConfig beanConfig = beanRoot.getBeanByName(className);
		List<MethodConfig> methodList = beanConfig.getMethodList();
		// 循环一下，看看哪些方法需要
		for (MethodConfig methodConfig : methodList) {
			String methodName = methodConfig.getName();
			String methodBody = methodConfig.getValue();
			if (StringUtils.isNotEmpty(methodName)) {
				// 得到这方法实例
				CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);
				if (ctmethod != null) {

					// 替换入参
					/*
					 * int staticIndex =
					 * Modifier.isStatic(ctmethod.getModifiers()) ? 0 : 1;
					 * MethodInfo methodInfo = ctmethod.getMethodInfo();
					 * CodeAttribute codeAttribute =
					 * methodInfo.getCodeAttribute(); LocalVariableAttribute
					 * localVariableAttribute =
					 * (LocalVariableAttribute)codeAttribute.getAttribute(
					 * LocalVariableAttribute.tag); for (int i = 0; i <
					 * ctmethod.getParameterTypes().length; i++) { int index =
					 * staticIndex + i ; String paramName =
					 * localVariableAttribute.variableName(index) ;
					 * System.out.println("paramName = "+paramName +"  "+index);
					 * }
					 */

					// 自动补充包名
					Collection<String> refClasses = new ArrayList<String>(ctclass.getRefClasses());
					String[] splits = methodBody.split("\\s");
					String regex = "^[A-Z].*?";
					StringBuilder content = new StringBuilder();
					for (String str : splits) {
						if (StringUtils.isBlank(str)) {
							content.append(str);
							continue;
						}
						content.append(" ");
						int bracket = str.indexOf("(");
						String str1 = "";
						if (bracket == 0) { // 例如： if(StringUtil.isEmpt())
							str1 = "(";
							str = str.substring(1, str.length());
						}
						if (str.matches(regex)) {
							int point = str.indexOf(".");
							String str2 = str;
							String str3 = "";
							if (point > 0) {
								str2 = str.substring(0, point);
								str3 = str.substring(point, str.length());
							} else if (bracket > 0) { // 例如： new Person();
								str2 = str.substring(0, bracket);
								str3 = str.substring(bracket, str.length());
							}
							String refNameStr = null;
							for (String refName : refClasses) {
								if (refName.endsWith(str2.trim())) {
									refNameStr = refName;
									break;
								}
							}
							if (refNameStr == null) {
								content.append(str1 + str);
							} else {
								content.append(str1 + refNameStr + str3);
							}
						} else {
							content.append(str1 + str);
						}

					}
					ctmethod.setBody(content.toString());
				}
			}
		}

		ctclass.detach();
		return ctclass.toBytecode();
	}

}
